package com.ruoyi.handler.websocket;

import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.manager.WebSocketChannelManager;
import com.ruoyi.web.service.TokenService;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Objects;

/**
 * 继承自WebSocketServerProtocolHandler，自定义处理客户端发送的websokcet消息，此时网络协议已升级为websokcet。
 *
 * @author Nanase Takeshi
 * @date 2022/6/2 11:40
 */
@Slf4j
public class MyWebSocketServerProtocolHandler extends WebSocketServerProtocolHandler {

    public static final String LOGIN_USER_KEY = "loginUser";

    public MyWebSocketServerProtocolHandler(String websocketPath) {
        super(websocketPath);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
        log.info("decode frame: {}", frame.getClass().getName());
        Channel channel = ctx.channel();
        log.info("decode channel: {}", channel);
        if (frame instanceof CloseWebSocketFrame) { //客户端调用websocket的close方法时服务端接收到的就是CloseWebSocketFrame类型的报文帧。
            //CloseWebSocketFrame closeWebSocketFrame = (CloseWebSocketFrame) frame;
            // //传递CloseWebSocketFrame的statusCode
            //AttributeKey<Integer> attributeKey = AttributeKey.valueOf("closeCode");
            //ctx.channel().attr(attributeKey).setIfAbsent(closeWebSocketFrame.statusCode());
            super.decode(ctx, frame, out);
        } else if (frame instanceof TextWebSocketFrame) { //客户端调用websocket的send方法时服务端接收到的就是TextWebSocketFrame类型的报文帧。
            //对每一个消息报文都要进行token校验。除了要校验当前连接对应的token是否存在，如果存在的情况下还要校验其是否已过期，
            // 即是否还能根据该token从redis中取到对应的已登录的用户信息。如果能取到说明对应用户登录状态未过期，否则表示已过期，服务器需主动断开当前websocket连接。
            String currentToken = WebSocketChannelManager.getTokenByWebsocketChannel(channel);
            log.info("decode currentToken: {}", currentToken);
            if (Objects.isNull(currentToken)) {
                log.warn("当前websocket连接异常, 服务器主动断开此连接!");
                WebSocketMessageHandler.sendCloseChannelMsg(channel, "当前websocket连接异常, 服务器主动断开此连接.");
            } else {
                TokenService tokenService = SpringUtils.getBean(TokenService.class);
                //根据token去缓存中查找对应已登录用户
                LoginUser loginUser = tokenService.getLoginUser(currentToken);
                if (Objects.isNull(loginUser)) { //说明当前currentToken对应的用户不是web端
                    loginUser = tokenService.getMiniprogramLoginUser(currentToken);
                }
                if (Objects.isNull(loginUser)) {
                    log.warn("用户未登录或登录状态已过期,服务器断开当前websocket连接!");
                    WebSocketMessageHandler.sendCloseChannelMsgAfterTokenExpired(channel, "用户未登录或登录状态已过期,服务器断开当前websocket连接.");
                } else {
                    //将当前连接所属的用户信息存入到channel中，以供后面需要时使用
                    AttributeKey<LoginUser> attributeKey = AttributeKey.valueOf(MyWebSocketServerProtocolHandler.LOGIN_USER_KEY);
                    ctx.channel().attr(attributeKey).setIfAbsent(loginUser);
                    super.decode(ctx, frame, out);
                }
            }
        }

    }


}
